import { useState, useCallback, useLayoutEffect, useRef } from 'react';
import type { RefObject } from 'react';

export type ElementType = Element | HTMLElement | null;
export type RefElementType = RefObject<ElementType>;
export type FunctionElementType = () => ElementType;
export type ParamType = RefElementType | FunctionElementType;
export type PositionType = Partial<{ x: number, y: number, isMove: boolean, maxX: number, maxY: number, minX: number, minY: number, centerX: number, centerY: number }>;
export type OptionType = Partial<{
    isLimitX: boolean,
    isLimitY: boolean,
    defaultPosition: {
        x: number,
        y: number
    }
}>;
const useAnyDrag = (el: ParamType, option: OptionType = { isLimitX: true, isLimitY: true,defaultPosition:{ x:0,y:0 } }, containerRef?: ParamType): PositionType => {
    const isMobile = navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i);
    const eventType = isMobile ? ['touchstart', 'touchmove', 'touchend'] : ['mousedown', 'mousemove', 'mouseup'];
    const element = useRef<ElementType>();
    const containerElement = useRef<ElementType>();
    const { isLimitX, isLimitY,defaultPosition } = option;
    const globalWidthHeight = {
        offsetWidth: window.innerWidth,
        offsetHeight: window.innerHeight
    }
    let isStart = false;
    const [position, setPosition] = useState<PositionType>({
        x: defaultPosition?.x,
        y: defaultPosition?.y,
        maxX: 0,
        maxY: 0,
        centerX: 0,
        centerY: 0,
        minX: 0,
        minY: 0
    });
    const [isMove, setIsMove] = useState(false);
    const downPosition = {
        x:0,
        y:0
    }
    const setOverflow = () => {
        const limitEle = (containerElement.current || document.body) as HTMLElement;
        if (isLimitX) {
            limitEle.style.overflowX = 'hidden';
        } else {
            limitEle.style.overflowX = '';
        }
        if (isLimitY) {
            limitEle.style.overflowY = 'hidden';
        } else {
            limitEle.style.overflowY = '';
        }
    }
    const onStartHandler = useCallback((e:Event) => {
        isStart = true;
        const target = element.current as HTMLElement;
        if (target) {
            target.style.cursor = 'move';
        } 
        const event: Touch | MouseEvent = e instanceof TouchEvent ? e.changedTouches[0] : e as MouseEvent;
        const { clientX, clientY } = event;
        downPosition.x = clientX - target.offsetLeft;
        downPosition.y = clientY - target.offsetTop;
        setOverflow();
        window.addEventListener(eventType[1], onMoveHandler);
        window.addEventListener(eventType[2], onUpHandler);
    }, []);
    const onMoveHandler = useCallback((e: Event) => {
        if (!isStart) {
            return;
        }
        setOverflow();
        const event: Touch | MouseEvent = e instanceof TouchEvent ? e.changedTouches[0] : e as MouseEvent;
        const { clientX, clientY } = event;
        if (!element.current) {
            return;
        }
        const { offsetWidth, offsetHeight} = element.current as HTMLElement;
        const { offsetWidth: containerWidth, offsetHeight: containerHeight } = (containerElement.current as HTMLElement || globalWidthHeight);
        const { x,y } = downPosition;     
        const moveX = clientX - x,
              moveY = clientY - y;     
        const data = {
            x: isLimitX ? Math.max(0, Math.min(containerWidth - offsetWidth, moveX)) : moveX,
            y: isLimitY ? Math.max(0, Math.min(containerHeight - offsetHeight, moveY)) : moveY,
            minX: 0,
            minY: 0,
            maxX: containerWidth - offsetWidth,
            maxY: containerHeight - offsetHeight,
            centerX: (containerWidth - offsetWidth) / 2,
            centerY: (containerHeight - offsetHeight) / 2
        }
        setIsMove(true);
        setPosition(data);
    }, []);
    const onUpHandler = useCallback(() => {
        const target = element.current as HTMLElement;
        if (target) {
            target.style.cursor = '';
        }
        isStart = false;
        setIsMove(false);
        const limitEle = (containerElement.current || document.body) as HTMLElement;
        limitEle.style.overflowX = '';
        limitEle.style.overflowY = '';
        window.removeEventListener(eventType[1], onMoveHandler);
        window.removeEventListener(eventType[2],onUpHandler);
    }, []);
    useLayoutEffect(() => {
        element.current = typeof el === 'function' ? el() : el.current;
        containerElement.current = typeof containerRef === 'function' ? containerRef() : containerRef?.current;
        if (!element.current) {
            return;
        }
        element.current.addEventListener(eventType[0], onStartHandler);
        return () => {
            element.current?.removeEventListener(eventType[0], onStartHandler);
        }
    }, []);
    return {
        ...position,
        isMove
    }
}
export default useAnyDrag;